/*****************************************************************************
*
*  PROJECT:     SlipStream
*               (shadows - shadows class)
*  LICENSE:     GNU General Public License v3 - http://www.gnu.org/licenses/gpl.html

*  DEVELOPERS:  Philip Farquharson 	<B00137077@studentmail.uws.ac.uk>
*				John Gray 			<B00145524@studentmail.uws.ac.uk>		
*				Zohaib Bhatti		<B00104280@studentmail.uws.ac.uk>
*				Euan Hislop 		<B00138636@studentmail.uws.ac.uk>
*
*****************************************************************************/

#include "shadows.h"

// light variables for the shadowing light source
GLfloat lightPos2[] = {5.0f, 4.0f, 4.0f};
GLfloat lightAmb[] = {0.5,0.5,0.5};
GLfloat lightDif[] = {1.0,1.0,1.0};
GLfloat materialAmb[] = {0.4,0.4,0.4};
GLfloat materialDif[] = {1.0,1.0,1.0};
GLfloat casterPos[] = {0.0,-2.5, 0.0}; 

// constructor
shadows::shadows()
{
}

// destructor
shadows::~shadows()
{
}

// functions to move the light source for the shadow
void shadows::setLightXPos(GLfloat xChange)
{
	lightPos2[0] -= xChange;
}

void shadows::setLightXNeg(GLfloat xChange)
{
	lightPos2[0] += xChange;
}

void shadows::setLightYPos(GLfloat yChange)
{
	lightPos2[1] += yChange;
}

void shadows::setLightYNeg(GLfloat yChange)
{
	lightPos2[1] -= yChange;
}

void shadows::setLightZPos(GLfloat zChange)
{
	lightPos2[2] -= zChange;
}

void shadows::setLightZNeg(GLfloat zChange)
{
	lightPos2[2] += zChange;
}

// initialise the light source
void shadows::init()
{
	glClearStencil(0);
	glLightfv(GL_LIGHT1, GL_AMBIENT, lightAmb);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, lightDif);
	glLightfv(GL_LIGHT1, GL_POSITION, lightPos2);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT1);	
}

// function to render the shadow
void shadows::renderShadow(GLfloat v1[], GLfloat v2[], GLfloat lightPosition[])
{	
	// SHADOW_DISTANCE should be a large value that will extend outside
	// the scene
	const float SHADOW_DISTANCE = 80.0f;
	GLfloat v3[3], v4[3];

	v3[0] = v1[0] + (v1[0]-lightPos2[0]) * SHADOW_DISTANCE;
	v3[1] = v1[1] + (v1[1]-lightPos2[1]) * SHADOW_DISTANCE;
	v3[2] = v1[2] + (v1[2]-lightPos2[2]) * SHADOW_DISTANCE;

	v4[0] = v2[0] + (v2[0]-lightPos2[0]) * SHADOW_DISTANCE;
	v4[1] = v2[1] + (v2[1]-lightPos2[1]) * SHADOW_DISTANCE;
	v4[2] = v2[2] + (v2[2]-lightPos2[2]) * SHADOW_DISTANCE;

	glBegin(GL_TRIANGLES);
		glVertex3fv(v1);
		glVertex3fv(v2);
		glVertex3fv(v4);
		glVertex3fv(v1);
		glVertex3fv(v4);
		glVertex3fv(v3);		
	glEnd();
}

// function to draw the floor that the shadow is being projected onto
void shadows::drawFloor()
{
	glScalef(3.0f ,1.0f ,1.0f);	
	glEnable(GL_TEXTURE_2D);
	glColor4f(1.0f, 1.0f, 1.0f, 0.8f);	
	glBegin(GL_QUADS);							// Begin drawing a quad
		glNormal3f(0.0f, 1.0f, 0.0f);				// Normal pointing up
		glTexCoord2f(1.0f, 1.0f);				// Bottom right of texture
		glVertex3f( 25.0f, 0.0f, 25.0f);				// Bottom right corner of floor
		glTexCoord2f(1.0f, 0.0f);				// Top right of texture
		glVertex3f( 25.0f, 0.0f,-25.0f);				// Top right corner of floor
		glTexCoord2f(0.0f, 0.0f);				// Top left of texture
		glVertex3f(-2.0f, 0.0f,-25.0f);				// Top left corner of floor
		glTexCoord2f(0.0f, 1.0f);				// Bottom left of texture
		glVertex3f(-2.0f, 0.0f, 25.0f);				// Bottom left corner of floor
	glEnd();
}

// function to draw the walls that the shadow is being projected onto
void shadows::drawWall()
{
	glBegin(GL_QUADS);
	glNormal3f(1.0f, 0.0f, 0.0f);
			// west
			glTexCoord2f(1.0f, 1.0f);
			glVertex3f(25.0f, 0.0f, -25.0f);
			glTexCoord2f(0.0f, 1.0f);
			glVertex3f(-2.0f, 0.0f, -25.0f);
			glTexCoord2f(0.0f, 0.0f);
			glVertex3f(-2.0f, 45.0f, -25.0f);
			glTexCoord2f(1.0f, 0.0f);
			glVertex3f(25.0f, 45.0f, -25.0f);

			// south		
			glTexCoord2f(1.0f, 1.0f);
			glVertex3f(25.0f, 0.0f, -25.0f);
			glTexCoord2f(0.0f, 1.0f);
			glVertex3f(25.0f, 0.0f, 75.0f);
			glTexCoord2f(0.0f, 0.0f);
			glVertex3f(25.0f, 45.0f, 75.0f);
			glTexCoord2f(1.0f, 0.0f);
			glVertex3f(25.0f, 45.0f, -25.0f);
		glEnd();
}

// function that renders the shadow and the objects that the shadow is being projected onto
void shadows::render()
{
	/// draw the light
	glDisable(GL_LIGHTING);
	glColor3f(1.0,1.0,1.0);
	glPushMatrix();
		glTranslatef(lightPos2[0],lightPos2[1],lightPos2[2]);
		glutSolidSphere(0.2,8,6);
	glPopMatrix();
	glEnable(GL_LIGHTING);

	glLightfv(GL_LIGHT1, GL_POSITION, lightPos2);
	// set the material properties
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, materialAmb);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, materialDif);
	
	//// draw the caster (triangle)
	//// find the vertices in world space	
	GLfloat v1[] = {casterPos[0] + 0.0, casterPos[1] + 0.0, casterPos[2] + 2.0};
	GLfloat v2[] = {casterPos[0] + -(2.0*0.866),casterPos[1] + 0.0,casterPos[2] - 0.5};
	GLfloat v3[] = {casterPos[0] + (2.0*0.866),casterPos[1] + 0.0,casterPos[2] + -0.5};

	glDisable(GL_LIGHTING);
	glColor3f(1.0,0.0,0.0);	
	glBegin(GL_TRIANGLES);
		glNormal3f(0.0,0.0,1.0);
		glVertex3fv(v1);
		glVertex3fv(v2);
		glVertex3fv(v3);
	glEnd();

	// draw the floor and walls
	glPushMatrix();
		glTranslatef(-50.0f, -6.9f, 12.0f);
		drawFloor();
		drawWall();
	glPopMatrix();

	// draw the ball 
	glDisable(GL_TEXTURE_2D);
	glPushMatrix();
	glColor3f(1.0, 1.0, 1.0);
		glTranslatef(-1.5,-4.0,0.0);
		glutSolidSphere(1.0,15,15);
	glPopMatrix();

	// save the following attributes
	glPushAttrib(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT | GL_POLYGON_BIT | GL_STENCIL_BUFFER_BIT);
		glDisable(GL_LIGHTING);	// disable lighting
		glDepthMask(GL_FALSE); // disable writing to the depth buffer
		glEnable(GL_STENCIL_TEST); // turn the stencil buffer on
		glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // disable writing to the color buffer		
		glEnable(GL_CULL_FACE); // enable culling

		glCullFace(GL_BACK); // cull the back faces
		glStencilFunc(GL_ALWAYS, 0x0, 0xff); // always pass the stencil test
		glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); // keep the current value when z-test fails and increment it when the z-test passes
		// render the first instance of shadow
		glPushMatrix();	
			// render shadow is called 3 times for each of the edges of the caster
			renderShadow(v1, v2, lightPos2);
			renderShadow(v2, v3, lightPos2);
			renderShadow(v3, v1, lightPos2);
		glPopMatrix();

		glCullFace(GL_FRONT); // cull the front faces
		glStencilFunc(GL_ALWAYS, 0x0, 0xff);
		glStencilOp(GL_KEEP, GL_KEEP, GL_DECR); // keep the current value when z-test fails and decrement it when the z-test passesd
		// render the second instance of shadow
		glPushMatrix();		
			renderShadow(v1, v2, lightPos2);
			renderShadow(v2, v3, lightPos2);
			renderShadow(v3, v1, lightPos2);
		glPopMatrix();

		glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // enable writing to the color buffer
		glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
		glStencilFunc(GL_NOTEQUAL, 0x0, 0xff);

		glEnable(GL_BLEND);
		glColor4f(0.0, 0.0, 0.0, 0.6);
		
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glDisable(GL_DEPTH_TEST);
		glDisable(GL_CULL_FACE);		

		glPushMatrix();
			glLoadIdentity(); // remove the model-view transform
				// draw over the entire viewport
				glBegin(GL_TRIANGLE_STRIP);
					glVertex3f(-1.0f, 1.0f, -1.0f);
					glVertex3f(-1.0f, -1.0f, -1.0f);
					glVertex3f(1.0f, 1.0f, -1.0f);
					glVertex3f(1.0f, -1.0f, -1.0f);
				glEnd();				
		glPopMatrix(); // restore the model-view matrix
		
		glDisable(GL_STENCIL_TEST); // disable stencil buffer testing
		glDepthMask(GL_TRUE); // enable writing to the depth buffer			
		
	glPopAttrib(); // pop the attributes that were initially popped
}